Alan Hsiao (ah668) and Grace Tan (gnt4)
ECE5725 Final Project
Fall 2020
This project takes the traditional game of hide-and-seek at home and merges it with technology and the outdoors.
Players need a phone with a cellular hotspot to play. Each player (a hider and a seeker) will use a Raspberry Pi system and a GPS module.
The hider will have 60 seconds to hide before each player will have the ability to see how far away the other player is.
Each player will randomly receive power ups during the game that will "zap" and temporarily disable the other player's screen.
Both the hider and seeker can move during the "seeking" phase of the game, but the hider must walk.
Since there are many components to this project, smaller python modules were made and tested independently.
Then, individual python modules were integrated together.
Below is a timeline of the progress of the project.
Item | Part Number | Quantity | Cost/Unit | Total Cost |
---|---|---|---|---|
GPS Module Receiver NEO-6M | NEO-6M | 2 | $10.99 | $21.98 |
2.8" TFT (320x240) with Resistive Touchscreen | 2298 | 2 | Free ($34.95) | Free ($69.90) |
Raspberry Pi 4 Model B - 1 GB RAM | 4295 | 2 | Free ($30.00) | Free ($60.00) |
Portable Charger Battery (2-Pack) | M850 | 1 | Free ($14.99) | Free ($24.99) |
USB-C Cable (2-Pack) | USB-C-G1 | 1 | Free ($5.99) | Free ($5.99) |
Hardware in the system is composed of two subsystems, which each have a GPS module, Raspberry Pi 4, Pi TFT, battery pack, and USB-C Cable.
Since the only piece of hardware that needed to be purchased were the GPS modules, the total for this project was $21.98.
Software in the system includes python modules for the hider and seeker. For various functionality, the following python packages were used:
When we first came up with the idea for this project, we envisioned our system using TCP or UDP to communicate between the two Raspberry Pis through the use of mobile hotspot.
However, we knew that we would have to break the problem down into smaller problems by adopting an incremental development design methodology.
The first iteration of our testing, we developed scripts that could communicate between two computers on the same WiFi network which allowed us to develop and test code faster.
This was done easily with the Python socket library to establish a TCP connection or send messages using UDP.
This code is available in the code appendix as tcp_ip_server.py, tcp_ip_client.py, udp_server.py, and udp_client.py.
Both UDP and TCP required the server and the client to connect to a specific port on a specific IP address.
Due to the design of the protocols, TCP is more reliable since it can verify whether or not the host receives a message whereas for UDP, the client never knows whether the message is received.
Since TCP is a stream-oriented connection, the client and host must establish a connection to send a stream of bytes, it has a larger overhead compared to UDP (which solely uses datagrams).
This led us to use UDP for our programs because of strict real-time requirements and relatively flexible packet drop constraints.
The second obstacle that we encountered was trying to connect two laptops on two different WiFi networks.
Since the laptops are now on separate networks, we are not able to use their private IP addresses as the IP address in the code.
Instead, we must utilize the router’s public IP address and change the settings to allow for port forwarding.
This allows a computer to send data to a specific port (in this case, port 20-21) on the other network’s router which will then forward the data to the other player’s computer.
We were able to successfully send messages to each other’s devices while on two different networks after changing the router's setting as shown below.
The next step was to find a way to change port forwarding settings on our mobile hotspots.
Since we were originally using our home networks, it was easy to change port forwarding settings.
However, as we did more research, we realized that mobile hotspots are protected behind an Internet Service Provider (ISP) firewall which does not allow for port forwarding (unless you pay a significant amount).
We had to quickly change gears and try a different way to communicate between the two Raspberry Pis.
After looking at many different Python libraries, we settled on using the class server as a meeting point for the two Raspberry Pis.
We created a Secure Shell (SSH) client on both Raspberry Pis to write and read files on the server as a way to communicate.
We leveraged a library called paramiko which requires the IP address, username, and password to connect to a server.
A bug that we ran into is that if a file is read while it's being written to, the readline will return as an empty string.
We were able to quickly patch our program by only taking in non-empty strings as parsed data.
In order to simplify the synchornization, we decided to design our communication protocol similar to UART where one file is used for sending data and another file is used for receiving data.
Additionally, we were able to easily send different types of data by using comma-delimited formatting.
Our initial tests were a success and can be viewed or executed with the serverRW.py file.
This establishes the communication link between the two RPi systems as shown below.
We first tested RPi communication indoors and then moved to using hotspot and doing so outdoors. While testing the fully integrated system with the GPS modules and pygame logic, we ran into issues of the screen freezing halfway through playing the game outside. At first, we thought it was a temperature problem with the RPi or GPS module. However, after looking at the data sheet, we saw that the cold temperature was not the issue. Finally, we figured out that the RPi froze in displaying the pygame screens because it was switching from our hotspot to wifi connection even when we were outside. Thus, we made sure to play this game away from any wifi connections the RPi may have when doing final testing.
The first GPS goal was to communicate with the GPS module and parse the data. We needed to get both working individually on each Raspberry Pi.
A challenge that we faced was cold-starting the GPS module as these GPS modules were brand new and made in China and acquiring signal took more than 20 minutes of waiting outside.
However, once the GPS modules acquired signal, they store the general location in memory such that if they are restarted, they will not need to cold-start again.
The next time we tried starting the GPS modules, it only took 1 minute to acquire a GPS lock.
By using a micro-USB cable, we were able to simplify the hardware and avoid circuit connectivity issues with headers.
To find which port to read from, we first looked at the devices and on the bus by typing in the command “lsusb” and saw that
the GPS module corresponds to U-Blox AG, device 4 as shown below.
Next, to find the associated driver, we used the “usb-devices” command, which displays detailed information on the devices connected to the USB. Below is part of the output that corresponds to the GPS module. We saw that the driver is cdc_acm so when looking at the various drivers under the dev directory, we know that the ttyACM0 is the corresponding driver.
We used the serial library to read the data from the GPS module as well as the pynmea2 library to parse the GPS data.
We parse each line and read the message if the sentence contains ‘GGA’, which means the sentence indicates the fixed data in GGA
NMEA format for the GPS module, allowing us to obtain GPS coordinates.
Now that the GPS coordinates can be obtained, we determined the distance using the Haversine formula, as shown below.
We saw that at best, when the GPS modules were directly next to each other, we were able to obtain a measurement of about 3-9
meters. In order to try to obtain more accurate data, we looked at the average altitude in collegetown to replace the averaged
Earth’s radius, but found that the mean radius actually produced better results than the collegetown radius. If we had additional
money to spend on this project, we would purchase a differential or carrier-phase tracking GPS module to get centimeter accuracy.
We believe that with additional tweaking, we could possibly get a more accurate reading, but for $10 GPS modules from Amazon
operating in the Ithaca weather, we believe this is a reasonable accuracy.
One issue that we ran into involved reading the GGA sentences from the GPS module. While creating the initial test script, we used
an if statement to check if the sentence was in GGA format. However, as we designed the game, we saw that our GPS location was updating
much more slowly than usual. This was because our module would simply check the sentence for GGA format, and if it wasn't, it would continue
on to the rest of the game logic. Since GGA sentences are only sent so often, we would be missing a lot of the GPS location updates as we
computed the game logic. While debugging the game, we changed the if statement to a while loop in order for the game to receive as many GGA
sentences as possible.
Our next goal was to develop an FSM description of our game such that we could simplify the code implementation. This was also helpful in creating screens for each different state. Each player has a control variable, which take the value from -1 to 7 inclusive. Thus, each state is based on the combination of each player's control variable. This control variable is communicated on the server so that each player can advance to the correct state in real time. Regardless of the control variable, each player will read and write its current state to the sever each time the screen updates. This ensures that the server as well as the Raspberry Pi both have the most up to date copy of the game variables. Below is the diagram of the finite state machine (FSM), which can be further broken down into the pre-game portion (top of the FSM), the game portion (bottom right of FSM), and the post-game portion (bottom left of FSM).
The pre-game section starts when one of the players join up until the countdown when the game starts.
When the player is not in the game, his or her control variable is -1. Once the player enters the game, their control variable updates
to 0. If the other player has not entered the game, there is a note in small red text to inform the player that the other player has
not joined. Having this note enhances the player experience so they are aware of the other player's state. The color red was chosen to
catch the player's attention but the text was smaller so it would not distract from the other parts of the screen, which are more
important. The more important sections are displayed in white text on a black background for best contrast. The center is where part
of the screen is where the distance is displayed. During the pre-game portion, there is no distance since the game has not started and
so the display shows "-- m". The right side shows the menu options, which corresponding to the 4 GPIO pins on the PiTFT display.
During any portion of the game, the player can quit, and so there is a "QUIT" option even if there is only one player in the game.
This "QUIT" button is the bottom most as the exit option is usually placed at the bottom-right-most portion of the screen.
Once both players enter into the game, they start in the "Initialization" state. The note is updated to reflect which player is either
hider or seeker and the menu has "READY" option at the top. When a player presses the "READY" option, his or her control variable is
updated to 1 and written to the server. Additionally, the local time for when the button is pressed recorded and written to the server.
This way, when a second player presses the ready button, they can make a comparison between their recorded time and the other player’s
recorded time.
We programmed the RPi to always reference the newer time (when the second player presses the ready button) as the “start” of the game
which solves synchronization and locking issues. This corresponds to the "Communicate Countdown Time" state. There is also a note
displayed that notifies the current player that the other player is waiting for them to press "READY".
From there, both players transition to the "Hiding Countdown" state where the hiding countdown starts for 60 seconds.
Since we have synchronized the game “start time” between the two RPis, we do not have to worry about latency or communication issues
since the time is both locally kept as well as synchronized over the internet network. During the countdown, the seeker will stay in
place for a minute while waiting for the hider to hide.
At any point in this countdown, if the hider feels that they are ready for the seeker to start, they can press the start button to
initialize a game start countdown. This is the "Hider Finish Early with Game Starting Countdown" state and the hider’s control variable is
updated to 3, which initializes a 5 second countdown timer with a note to inform the seeker that the hider is done hiding.
Otherwise, the game will continue to countdown until 60 seconds have passed. Below are the screens for the pre-game portion.
The game section starts when the countdown ends and the seeker starts to look for the hider. At this point, both the hider
and the seeker can move, but the hider can only walk. The seeker will be able to use the relative distance to get an idea of
the general location of the hider while the hider will know if the seeker is getting closer or further away. Both players' control
variables are set to 4 during the play game phase. During this state, each player's RPi will read the serial port to obtain
the GPS data. It will wait until the GPS data sent is a NMEA GGA sentence (which means that GPS lock has been acquired) before parsing it.
We use the pynmea2 library for parsing the data into latitude and longitude. The latitude and longitude are then passed into the
Haversine formula (along with the other Pi's location which was read from the server). If there is no GPS signal, then the calculated
distance will be absurdly large and the number will be replaced by "## m" on the screen.
A critical feature to have in any type of game is the ability to pause. We implemented our pause functionality by changing to another state.
When one player presses the pause button during the game, their control variable will change to 5. This causes both their screen as well as
the other player's screen to show the "PAUSED" text. While the game is paused, the other player also has the option to press pause if they
must attend to anything, but both players must be unpaused (hit the resume button) in order for the game to start again. Additionally,
there will be a note at the bottom of the screen indicating which players have hit pause. Similar to the pre-game state, the ability to quit
at any time is still in place.
Another feature that we implemented in the play game state is the ability to gain power ups. Each time the screen updates, the code will
roll a random number between 0 and 100. If the number is 0, the player will receive a power up called "ZAP". This power up can be used by
pressing the button and will temporarily disable the other player's ability to pause and see the relative distance. The zap feature was
implemented by briefly changing the player's control variable to 7 for 10 seconds. While the other player's control variable is 7, the
current player's screen will show "?? m" instead of the actual distance. We noticed that this gives at least one player a zap about once
every minute and is a good pace for fast-paced games. One thing to note that is if a player is zapped while having
a power up, they can still zap the other player while they are zapped.
Since it can still sometimes be difficult to find the hider if they are good at hiding, we wanted to implement a confirmation from the
seeker that they have indeed found the hider. Once the seeker is less than 10 meters away from the hider, they will see a button on the right
side of the screen to "FINISH" the game. Pressing the "FINISH" button will move the game into "post-Game" section by setting the seeker's
control variable to 6. The hider's Raspberry Pi will then see that the seeker has found the hider and also set its control variable to 6.
For the post-game section, players will see that the game is over because the hider has been found. There are several options that players
can choose from at this point. They can either play again, quit the game, or wait to see what the other player wants to do. If a player presses
restart, they will be taken back to the initialization screen and their control variable will be set to 0. Additionally, the other player that
has yet to hit the restart button will see a message that the other player wants to play again.
If at any point, for any reason, one of the players does not want to continue to play the game, they can press the quit button twice to leave
the game. If they only press quit once, a quit confirm message will remain on the screen for 5 seconds and then revert back to requiring two
quit button presses to leave the game. When one player has quit the game, the other player will also see a note saying that the other player
has left the game to ensure that neither player is waiting too long to determine the other player's intentions. Similar to what was mentioned
in the pre-game section, players that choose to quit the game will set their control variable to -1 before exiting to ensure that the other
player knows.
After many hours of design, testing, and research, the Raspberry Pi Hide and Seek game is fully working and a great way to spend time outdoors
with family and friends during quarantine. The system works as intended and gives players a responsive and easy to use graphical user interface (GUI).
Additionally, the GPS modules are extremely responsive to movement and it was easy to tell when the seeker was starting to get closer and closer even
while hiding behind objects. There are several key factors that make the game an enjoyable experience. First, as stated in the GPS Data section, we
were able to bypass all jumpers and breadboards by purchasing a GPS module that had a micro-USB port. This allowed us to simply connect the micro-USB
port on the GPS to a USB port on the Raspberry Pi and makes it much easier to run around without electrical failures. Second, the responsive GUI allows
players to know the other player's intention without being next to them. The notes on the bottom of the screen allow you to quickly know what the other
player wants to do. For example, if the other player wants to start the game or quit the game, it will show that on the screen. Lastly, our game is
able to reduce unnecessary writes to the server during game play by only reading and writing every time the GPS module acquires a new lock or location.
This extra precaution results in less server latency and faster distance calculation times.
Though we ran into several issues as we created this game, the end result works perfectly and meets the goals outlined in the description. To reiterate,
several of the issues that we ran into include (but are not limited to): cold-starting the GPS module and acquiring a lock, finding the serial port of
the GPS module, communicating between two computers on the same network, communicating between two computers on different networks, communicating between
two computers on mobile hotspots, using the server as a communication point, writing and reading files at the same time, switching networks between WiFi
and mobile hotspot, server latency due to excessive writing, GUI layout, parsing server and GPS data, communicating global time, and working out the
power up logic. As explained in the RPi communication section, we realized somewhat late that we would not be able to use TCP/UDP for communicating between
the two Raspberry Pis due to mobile hotspot, but we were able to successfully innovate around it and get a working game.
If we had more time to explore additional implementations, we would add an intertial measurement unit (IMU) such that either the hider or seeker would only know the direction that the other player is coming from instead of the distance. Also, it would make the GUI easier to use if we integrated the code together and gave users the option to select if they want to be the hider or the seeker at the beginning of the game. Additionally, we thought about a harder version of the game where there are no distances displayed but there are colors where red indicates that the seeker is getting closer to the hider, blue for farther away, and green for within a 10 meter radius. Another thing that we could implement is experimenting with penalizing the hider if they are moving too fast. This could either be in the form of temporarily disabling their screen or giving the seeker more zaps than hider. Lastly, one additional feature that we hope to implement is the ability to have multiple hiders and as they are found, change their roles to seekers.
Item | Description |
---|---|
GPS Module Purchasing | GPS Module: NEO-6M |
GPS Module Manual | GPS Module: NEO-6M |
PYNMEA2 Library | GPS Sentence Parsing |
Paramiko Library | Server SSH and Reading/Writing |
PyGame Library | Graphical User Interface |
Random Library | Obtaining Power Ups |
Haversine Formula | Haversine Formula |
Math Library | Haversine Formula |
Decimal Library | Graphical User Interface |
Linux USB | USB Devices |
ah668@cornell.edu
BS in Electrical & Computer Engineer '21
MEng in Electrical & Computer Engineer '21
Work Distribution:
gnt4@cornell.edu
BS in Electrical & Computer Engineer '20
MEng in Electrical & Computer Engineer '21
Work Distribution:
Below, we have provided our code for the hider and seeker. Our full repository with other code we used to help with incremental testing can be found here.
# Grace Tan (gnt4) and Alan Hsiao (ah668)
# ECE 5725 - Embedded OS - Final Project
# Wednesday Night Lab
# Displaying screen for hider
import os, sys, serial, time, math, random
import RPi.GPIO as GPIO
import pygame
from pygame.locals import * # event MOUSE variables
import pynmea2
import paramiko
import warnings
from decimal import Decimal
from cryptography.utils import DeprecatedIn25
############################################################################
# RPI Communication
############################################################################
warnings.simplefilter('ignore', DeprecatedIn25)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Changed to generic information for security purposes
ssh.connect('class_sever_ip', username='my_netid', password='my_password')
ftp = ssh.open_sftp()
# GPS Module
ser = serial.Serial('/dev/ttyACM0', 9600, timeout = 5)
############################################################################
# Pygame
############################################################################
# Setup environment variables to display piTFT
os.putenv('SDL_VIDEODRIVER', 'fbcon') # Display on piTFT
os.putenv('SDL_FBDEV', '/dev/fb0') #
os.putenv('SDL_MOUSEDRV', 'TSLIB') # Track mouse clicks on piTFT
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
# Start pygame
pygame.init()
pygame.mouse.set_visible(False)
# Variables for colors
WHITE = 255, 255, 255
BLACK = 0, 0, 0
RED = 255, 0, 0
GREEN = 0, 255, 0
BLUE = 0, 0, 255
# Set screen variables
size = width, height = 320,240
screen = pygame.display.set_mode(size)
center = 160,120
# Fonts
my_font_distance = pygame.font.Font(None, 70)
my_font_note = pygame.font.Font(None, 15)
my_font_button = pygame.font.Font(None, 20)
# Button
button1_text = 'READY'
button2_text = 'QUIT'
button4_text = 'ZAP'
button1_pos = 290, 40
button2_pos = 290, 225
button4_pos = 290, 102
my_buttons = {button1_text:button1_pos, button2_text:button2_pos}
# Note
note_text = "YOU ARE THE HIDER"
note_pos = 160,180
my_note = {note_text:note_pos}
# Distance
distance_text = "-- m"
distance_pos = center
my_distance = {distance_text:distance_pos}
# Function to display text
def update_text(font,text, pos, color):
text_surface = font.render(text, True, color)
rect = text_surface.get_rect(center = pos)
screen.blit(text_surface,rect)
############################################################################
# GPIO
############################################################################
# Set for broadcom numbering
GPIO.setmode(GPIO.BCM)
# Setup piTFT buttons from top to bottom
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Define events
def GPIO_17_callback(channel):
global my_control, other_control, my_control_time
# If you press READY
if my_control == 0:
my_control = 1
my_control_time = time.time() + 64 #60 sec for actual game
# If hider is ready first
elif my_control == 2 and other_control == 2:
my_control = 3
my_control_time = time.time() + 5
# If you press RESTART
elif my_control == 6:
my_control = 0
# If you press PAUSE
elif my_control == 4 and (other_control == 4 or other_control == 5):
my_control = 5
# If you press RESUME
elif my_control == 5 and other_control >= 4:
my_control = 4
def GPIO_22_callback(channel):
global my_control, has_powerup, powerup_time
# If you press ZAP
if has_powerup == 1:
powerup_time = time.time() + 10
my_control = 7
has_powerup = 0
def GPIO_27_callback(channel):
global quit_confirmed, quit_pressed_time
# If you press QUIT
quit_confirmed += 1
if quit_confirmed == 1:
quit_pressed_time = time.time()
# Add events for GPIO
GPIO.add_event_detect(17, GPIO.FALLING, callback=GPIO_17_callback, bouncetime=300)
GPIO.add_event_detect(22, GPIO.FALLING, callback=GPIO_22_callback, bouncetime=300)
GPIO.add_event_detect(27, GPIO.FALLING, callback=GPIO_27_callback, bouncetime=300)
############################################################################
# Game
############################################################################
# Game Control Variables
# -1 -> not in game
# 0 -> initialization
# 1 -> pressed READY
# 2 -> hider countdown
# 3 -> hider finishes earlier - game starting countdown
# 4 -> play game
# 5 -> pressed PAUSE
# 6 -> game over
# 7 -> pressed ZAP
my_control = 0
other_control = 0
# Other Game Variables
my_control_time = 0 # Time to stop for countdowns
other_control_time = 0 # Time to stop for countdowns
powerup_time = 0 # Time when powerup finishes
has_powerup = 0 # 1 when player has powerup and hasn't used it
random_num = 1 # Random number rolled
quit_confirmed = 0 # 0 = not pressed, 1 = pressed QUIT, 2 = quit confirm
quit_pressed_time = time.time()
init_time = time.time()
# GPS Variables
my_lat = 0
my_lon = 0
other_lat = 0
other_lon = 0
pi_distance = 0
while quit_confirmed < 2:
# Write and read controls information to communicate to other RPi
file_write=ftp.file('/home/gnt4/FinalProject/h2s.txt', "w", -1)
file_write.write(str(my_control)+','+str(my_control_time)+','+str(my_lat)+','+str(my_lon))
file_write.flush()
file_read=ftp.file('/home/gnt4/FinalProject/s2h.txt',"r", -1)
receive_controls = file_read.read()
if (len(receive_controls) > 0):
parsed_receive_controls = receive_controls.split(',')
other_control = int(parsed_receive_controls[0])
other_control_time = float(parsed_receive_controls[1])
other_lat = float(parsed_receive_controls[2])
other_lon = float(parsed_receive_controls[3])
file_read.flush()
# When QUIT is pressed, confirm quitting game with 5 second timeout
if quit_confirmed == 1:
button2_text = 'CONFIRM QUIT?'
button2_pos = 255,225
if (quit_pressed_time + 5 < time.time()):
quit_confirmed = 0
button2_text = 'QUIT'
button2_pos = 290, 225
my_buttons = {button1_text:button1_pos, button2_text:button2_pos}
# When game is being played and unpaused
if (my_control == 4 or my_control == 7) and (other_control == 4 or other_control == 7):
if time.time() > powerup_time:
my_control = 4
# Acquire GPS Data
gps_line = ser.readline()
if len(gps_line) == 0:
print("Time out! exit.\n")
sys.exit()
while gps_line.find('GGA') < 0:
gps_line = ser.readline()
gps_data = pynmea2.parse(gps_line)
my_lat = float(gps_data.latitude)
my_lon = float(gps_data.longitude)
# Calculate GPS Distance with Haversine Formula
lat1 = math.radians(my_lat)
lat2 = math.radians(other_lat)
lon1 = math.radians(my_lon)
lon2 = math.radians(other_lon)
dlon = lon2 - lon1
dlat = lat2 - lat1
earth_radius = 6373 # Average earth radius in km
h1 = pow(math.sin(dlat/2),2) + math.cos(lat1) * math.cos(lat2) * pow(math.sin(dlon/2),2)
h2 = 2 * math.atan2( math.sqrt(h1), math.sqrt(1-h1) )
pi_distance = round((earth_radius * h2 * 1000),2)
# When a random number is generated since there is currently no powerup
if has_powerup == 0:
random_num = random.randint(0,100)
# if you get powerup
if random_num == 0:
has_powerup = 1
button4_text = 'ZAP'
else:
button4_text = ''
# Print distance unless if there is no signal]
if pi_distance > 10000:
my_distance = {'## m':distance_pos}
elif other_control == 7:
my_distance = {'?? m':distance_pos}
else:
my_distance = {str(pi_distance) + ' m':distance_pos}
# Clear pause note
my_note = {note_text:note_pos}
if (other_control == 4 and my_control != 7):
my_buttons = {'PAUSE':button1_pos, button2_text:button2_pos, button4_text:button4_pos}
else:
my_buttons = {'':button1_pos, button2_text:button2_pos, button4_text:button4_pos}
# If other player finds you
elif other_control == 6 and my_control == 4:
my_control = 6
# If you and the other player have not restarted
elif my_control == 6 and other_control == 6:
my_distance = {'GAME OVER':distance_pos}
my_note = {'THANKS FOR PLAYING!':note_pos}
my_buttons = {'RESTART':button1_pos, button2_text:button2_pos}
# If you have not restarted and the other player has
elif my_control == 6 and (other_control == 0 or other_control == 1):
my_distance = {'GAME OVER':distance_pos}
my_note = {'SEEKER WANTS TO PLAY AGAIN!':note_pos}
my_buttons = {'RESTART':button1_pos, button2_text:button2_pos}
# If you have not restarted and the other player has
elif my_control == 6 and (other_control == -1):
my_distance = {'GAME OVER':distance_pos}
my_note = {'SEEKER HAS LEFT THE GAME':note_pos}
my_buttons = {'RESTART':button1_pos, button2_text:button2_pos}
# If game paused by both players
elif my_control == 5 and other_control == 5:
my_distance = {'PAUSED':distance_pos}
my_note = {'SEEKER ALSO PAUSED':note_pos}
my_buttons = {'RESUME':button1_pos, button2_text:button2_pos}
# If game paused only by you
elif my_control == 5 and other_control == 4:
my_distance = {'PAUSED':distance_pos}
my_note = {'':note_pos}
my_buttons = {'RESUME':button1_pos, button2_text:button2_pos}
# If game paused only by other player
elif my_control == 4 and other_control == 5:
my_distance = {'PAUSED':distance_pos}
my_note = {'WAITING FOR SEEKER TO UNPAUSE...':note_pos}
my_buttons = {'PAUSE':button1_pos, button2_text:button2_pos}
# Game Countdown
elif my_control == 3:
if my_control_time > time.time():
my_distance = {str(int(my_control_time - time.time())) + " s":distance_pos}
my_note = {'GAME IS STARTING':note_pos}
my_buttons = {'':button1_pos, button2_text:button2_pos}
else:
my_control = 4
# Hider Hiding
elif my_control == 2 and other_control >= 2:
if my_control_time > time.time():
my_distance = {str(int(my_control_time - time.time())) + " s":distance_pos}
my_note = {'HIDE BEFORE THE COUNTDOWN ENDS':note_pos}
my_buttons = {'START':button1_pos, button2_text:button2_pos}
else:
my_control = 4
# If both players ready, start countdown
elif my_control == 1 and other_control >= 1:
if(my_control_time < other_control_time):
my_control_time = other_control_time
my_control = 2
# You are not ready but other player is
elif my_control == 0 and other_control == 1:
my_note = {'SEEKER IS READY':note_pos}
# You are ready but other player is not
elif my_control == 1 and other_control == 0:
my_distance = {'-- m':distance_pos}
my_note = {'SEEKER IS NOT READY':note_pos}
my_buttons = {'':button1_pos, button2_text:button2_pos}
# When other player leaves game
elif other_control == -1:
my_control = 0
my_distance = {'-- m':distance_pos}
my_note = {'SEEKER IS NOT IN GAME':note_pos}
my_buttons = {'':button1_pos, button2_text:button2_pos}
else:
my_distance = {'-- m':distance_pos}
my_note = {'YOU ARE THE HIDER':note_pos}
my_buttons = {'READY':button1_pos, button2_text:button2_pos}
# Process touch on screen
for event in pygame.event.get():
if(event.type is MOUSEBUTTONDOWN):
pos = pygame.mouse.get_pos()
elif(event.type is MOUSEBUTTONUP):
pos = pygame.mouse.get_pos()
x,y = pos
# Update all the buttons and text on the screen
screen.fill(BLACK)
for my_text, text_pos in my_distance.items():
update_text(my_font_distance,my_text,text_pos,WHITE)
for my_text, text_pos in my_note.items():
update_text(my_font_note,my_text,text_pos,RED)
for my_text, text_pos in my_buttons.items():
update_text(my_font_button,my_text,text_pos,WHITE)
pygame.display.flip()
GPIO.cleanup()
pygame.quit()
############################################################################
# RPI Communication Cleanup
############################################################################
my_control = -1
file_write=ftp.file('/home/gnt4/FinalProject/h2s.txt', "w", -1)
file_write.write(str(my_control)+','+str(my_control_time)+','+str(my_lat)+','+str(my_lon))
file_write.flush()
ftp.close()
ssh.close()
# Grace Tan (gnt4) and Alan Hsiao (ah668)
# ECE 5725 - Embedded OS - Final Project
# Wednesday Night Lab
# Displaying screen for seeker
import os, sys, serial, time, math, random
import RPi.GPIO as GPIO
import pygame
from pygame.locals import * # event MOUSE variables
import pynmea2
import paramiko
import warnings
from decimal import Decimal
from cryptography.utils import DeprecatedIn25
############################################################################
# RPi Communication
############################################################################
warnings.simplefilter('ignore', DeprecatedIn25)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Changed to generic information for security purposes
ssh.connect('class_sever_ip', username='my_netid', password='my_password')
ftp = ssh.open_sftp()
# GPS Module
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
############################################################################
# Pygame
############################################################################
# Setup environment variables to display piTFT
os.putenv('SDL_VIDEODRIVER', 'fbcon') # Display on piTFT
os.putenv('SDL_FBDEV', '/dev/fb0') # Run on RPi and will display on piTFT
os.putenv('SDL_MOUSEDRV', 'TSLIB') # Track mouse clicks on piTFT
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
# Start pygame
pygame.init()
pygame.mouse.set_visible(False)
# Variables for colors
WHITE = 255, 255, 255
BLACK = 0, 0, 0
RED = 255, 0, 0
GREEN = 0, 255, 0
BLUE = 0, 0, 255
# Set screen and coordinates
size = width, height = 320, 240
screen = pygame.display.set_mode(size)
center = 160, 120
# Fonts
my_font_distance = pygame.font.Font(None, 70)
my_font_button = pygame.font.Font(None, 20)
my_font_note = pygame.font.Font(None,15)
# Buttons
button1_text = 'READY' # default is READY
button2_text = 'QUIT'
button3_text = ''
button4_text = ''
button1_pos = 290, 40
button2_pos = 290, 225
button3_pos = 290, 162
button4_pos = 290, 102
my_buttons = {button1_text:button1_pos, button2_text:button2_pos}
# Notes/Messages
note_text = 'YOU ARE THE SEEKER'
note_pos = 160, 180
my_note = {note_text:note_pos}
# Distance
distance_text = "-- m"
distance_pos = center
my_distance = {distance_text:distance_pos}
# Function to display text
def update_text(font,text, pos, color):
text_surface = font.render(text, True, color)
rect = text_surface.get_rect(center = pos)
screen.blit(text_surface,rect)
############################################################################
# GPIO
############################################################################
# Set for broadcom numbering
GPIO.setmode(GPIO.BCM)
# Setup piTFT buttons from top to bottom
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Define events
def GPIO_17_callback(channel):
global my_control, other_control, my_control_time
# If you press READY
if my_control == 0:
my_control = 1
my_control_time = time.time() + 64 # 60 sec for actual game
# If you press RESTART
elif my_control == 6:
my_control = 0
# If you press PAUSE
elif my_control == 4 and (other_control == 4 or other_control == 5):
my_control = 5
# If you press RESUME
elif my_control == 5 and other_control >= 4:
my_control = 4
def GPIO_22_callback(channel):
global has_powerup, powerup_time, my_control
# If you press ZAP
if has_powerup == 1:
powerup_time = time.time() + 10
my_control = 7
has_powerup = 0
def GPIO_23_callback(channel):
global my_control, other_control, can_finish
# If you press FINISH
if my_control == 4 and other_control == 4 and can_finish:
my_control = 6
def GPIO_27_callback(channel):
global quit_confirmed, quit_pressed_time
# If you press QUIT
quit_confirmed += 1
if quit_confirmed == 1:
quit_pressed_time = time.time()
# Add events for GPIO
GPIO.add_event_detect(17, GPIO.FALLING, callback=GPIO_17_callback, bouncetime=300)
GPIO.add_event_detect(22, GPIO.FALLING, callback=GPIO_22_callback, bouncetime=300)
GPIO.add_event_detect(23, GPIO.FALLING, callback=GPIO_23_callback, bouncetime=300)
GPIO.add_event_detect(27, GPIO.FALLING, callback=GPIO_27_callback, bouncetime=300)
############################################################################
# Game
############################################################################
# Game Control Variables
# -1 -> not in game
# 0 -> initialization
# 1 -> pressed READY
# 2 -> hider countdown
# 3 -> hider finishes earlier - game starting countdown
# 4 -> play game
# 5 -> pressed PAUSE
# 6 -> game over
# 7 -> pressed ZAP
my_control = 0
other_control = 0
# Other Game Variables
my_control_time = 0 # Time to stop for countdowns
other_control_time = 0 # Time to stop for countdowns
powerup_time = 0 # Time when powerup finishes
has_powerup = 0 # 1 when player has powerup and hasn't used it
random_num = 1 # Random number rolled
can_finish = 0 # 0 = does not show button, 1 = show button if less than 10 m
quit_confirmed = 0 # 0 = not pressed, 1 = pressed QUIT, 2 = quit confirm
quit_pressed_time = time.time()
init_time = time.time()
# GPS Variables
my_lat = 0
my_lon = 0
other_lat = 0
other_lon = 0
pi_distance = 0
while quit_confirmed < 2:
# Write and read controls information to communicate to other RPi
file_write=ftp.file('/home/gnt4/FinalProject/s2h.txt', "w", -1)
file_write.write(str(my_control)+','+str(my_control_time)+','+str(my_lat)+','+str(my_lon))
file_write.flush()
file_read=ftp.file('/home/gnt4/FinalProject/h2s.txt', "r", -1)
received_controls = file_read.read()
if len(received_controls) > 0:
parsed_receive_controls = received_controls.split(',')
other_control = int(parsed_receive_controls[0])
other_control_time = float(parsed_receive_controls[1])
other_lat = float(parsed_receive_controls[2])
other_lon = float(parsed_receive_controls[3])
file_read.flush()
# When QUIT is pressed, confirm quitting game with 5 second timeout
if quit_confirmed == 1:
button2_text = "CONFIRM QUIT?"
button2_pos = 255,225
if quit_pressed_time + 5 < time.time():
quit_confirmed = 0
button2_text = "QUIT"
button2_pos = 290,225
my_buttons = {button1_text:button1_pos, button2_text:button2_pos}
# When game is being played and unpaused
if (my_control == 4 or my_control == 7) and (other_control == 4 or other_control == 7):
if time.time() > powerup_time:
my_control = 4
# Acquire GPS Data
gps_line = ser.readline()
if len(gps_line) == 0:
print("Time out! exit.\n")
sys.exit()
# if gps_line.find('GGA') > 0:
while gps_line.find('GGA') < 0:
gps_line = ser.readline()
gps_data = pynmea2.parse(gps_line)
my_lat = float(gps_data.latitude)
my_lon = float(gps_data.longitude)
# Calculate GPS Distance with Haversine Formula
lat1 = math.radians(my_lat)
lon1 = math.radians(my_lon)
lat2 = math.radians(other_lat)
lon2 = math.radians(other_lon)
dlon = lon2-lon1
dlat = lat2-lat1
earth_radius = 6373 # Average earth radius in km
h1 = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
h2 = 2 * math.atan2(math.sqrt(h1), math.sqrt(1 - h1))
pi_distance = round((earth_radius * h2 *1000),2)
# When a random number is generated since there is currently no powerup
if has_powerup == 0:
random_num = random.randint(0,100)
# If you get a powerup
if random_num == 0:
has_powerup = 1
button4_text = 'ZAP'
else:
button4_text = ''
# Print distance unless if there is no signal or other player has powerup
if pi_distance > 10000:
can_finish = 0
my_distance = {'## m':distance_pos}
button3_text = ''
elif other_control == 7:
my_distance = {'?? m':distance_pos}
button3_text = ''
else:
my_distance = {str(pi_distance)+' m':distance_pos}
if pi_distance < 10:
can_finish = 1
button3_text = 'FINISH'
else:
can_finish = 0
button3_text = ''
# Pause button and note
if other_control == 4 and my_control != 7:
button1_text = 'PAUSE'
else:
button1_text = ''
my_note = {note_text:note_pos}
my_buttons = {button1_text:button1_pos, button2_text:button2_pos, button3_text:button3_pos, button4_text: button4_pos}
# If game is paused by both players
elif my_control == 5 and other_control == 5:
my_distance = {'PAUSED':distance_pos}
my_note = {'HIDER ALSO PAUSED':note_pos}
my_buttons = {'RESUME':button1_pos, button2_text:button2_pos}
# If game is paused by you
elif my_control == 5 and other_control == 4:
my_distance = {'PAUSED':distance_pos}
my_note = {'':note_pos}
my_buttons = {'RESUME':button1_pos, button2_text:button2_pos}
# If game is paused by other player
elif my_control == 4 and other_control == 5:
my_distance = {'PAUSED':distance_pos}
my_note = {'WAITING FOR HIDER TO UNPAUSE...':note_pos}
my_buttons = {'PAUSE':button1_pos, button2_text:button2_pos}
# If game is finished and other player has restarted
elif my_control == 6 and (other_control == 0 or other_control == 1):
my_distance = {'GAME OVER':distance_pos}
my_note = {'HIDER WANTS TO PLAY AGAIN!':note_pos}
my_buttons = {'RESTART':button1_pos, button2_text:button2_pos}
# If game is finished and other player has left game
elif my_control == 6 and (other_control == -1):
my_distance = {'GAME OVER':distance_pos}
my_note = {'HIDER HAS LEFT GAME':note_pos}
my_buttons = {'RESTART':button1_pos, button2_text:button2_pos}
# If game is finished
elif my_control == 6:
my_distance = {'GAME OVER':distance_pos}
my_note = {'THANKS FOR PLAYING!':note_pos}
my_buttons = {'RESTART':button1_pos, button2_text:button2_pos}
# If hider finishes hiding before countdown
elif my_control == 2 and (other_control == 3 or other_control == 4):
if other_control_time > time.time():
my_distance = {str(int(other_control_time-time.time()))+' s':distance_pos}
my_note = {'HIDER IS DONE! GAME IS STARTING':note_pos}
my_buttons = {'':button1_pos, button2_text:button2_pos}
else:
my_control = 4
# Hider Hiding
elif my_control == 2 and other_control >= 2:
if my_control_time > time.time():
my_distance = {str(int(my_control_time-time.time()))+' s':distance_pos}
my_note = {'HIDER IS HIDING':note_pos}
my_buttons = {'':button1_pos, button2_text:button2_pos}
else:
my_control = 4
# If both players have pressed START
elif my_control == 1 and other_control >= 1:
if my_control_time < other_control_time:
my_control_time = other_control_time
my_control = 2
# If game has not started and only the other player has pressed READY
elif my_control == 0 and other_control == 1:
my_note = {'HIDER IS READY':note_pos}
# If game has not started and only you have pressed READY
elif my_control == 1 and other_control == 0:
my_distance = {'-- m':distance_pos}
my_note = {'HIDER IS NOT READY':note_pos}
my_buttons = {'':button1_pos, button2_text:button2_pos}
# If other player leaves game
elif other_control == -1:
my_control = 0
my_distance = {'-- m':distance_pos}
my_note = {'HIDER IS NOT IN GAME':note_pos}
my_buttons = {'':button1_pos, button2_text:button2_pos}
# If game has not begun
else:
my_distance = {'-- m':distance_pos}
my_note = {'YOU ARE THE SEEKER':note_pos}
my_buttons = {'READY':button1_pos, button2_text:button2_pos}
# Process touch on screen
for event in pygame.event.get():
if(event.type is MOUSEBUTTONDOWN):
pos = pygame.mouse.get_pos()
elif(event.type is MOUSEBUTTONUP):
pos = pygame.mouse.get_pos()
# Update all the buttons and text on the screen
screen.fill(BLACK)
for my_text, text_pos in my_distance.items():
update_text(my_font_distance,my_text,text_pos,WHITE)
for my_text, text_pos in my_buttons.items():
update_text(my_font_button,my_text,text_pos,WHITE)
for my_text, text_pos in my_note.items():
update_text(my_font_note,my_text,text_pos,RED)
pygame.display.flip()
GPIO.cleanup()
pygame.quit()
############################################################################
# RPI Communication Cleanup
############################################################################
my_control = -1
file_write=ftp.file('/home/gnt4/FinalProject/s2h.txt', "w", -1)
file_write.write(str(my_control)+','+str(my_control_time)+','+str(my_lat)+','+str(my_lon))
file_write.flush()
ftp.close()
ssh.close()